package builder

import (
	"log"
	"os"
	"runtime"
	"strings"

	"gitee.com/go-wena/cli"
	"gitee.com/go-wena/env"
	"github.com/ghodss/yaml"
)

func New() *Builder {
	return (&Builder{
		//Version:     "0.0.0",
		LDFlags:     "-s -w",
		GOOS:        runtime.GOOS,
		GOARCH:      runtime.GOARCH,
		GOPROXY:     env.GetString("GOPROXY", "https://goproxy.cn,direct"),
		GO111MODULE: env.GetString("GO111MODULE", "on"),
	}).FromEnv()
}

type Builder struct {
	Output      string   `json:"output,omitempty" yaml:"output,omitempty"`               //
	NotRun      bool     `json:"not_run,omitempty" yaml:"not_run,omitempty"`             //
	NoCache     bool     `json:"no_cache,omitempty" yaml:"no_cache,omitempty"`           //
	Verbose     bool     `json:"verbose,omitempty" yaml:"verbose,omitempty"`             //
	X           bool     `json:"x,omitempty" yaml:"x,omitempty"`                         //
	KeepWorkDIR bool     `json:"keep_work_dir,omitempty" yaml:"keep_work_dir,omitempty"` //
	Race        bool     `json:"race,omitempty" yaml:"race,omitempty"`                   //
	LDFlags     string   `json:"ld_flags,omitempty" yaml:"ld_flags,omitempty"`           //
	ASMFlags    string   `json:"asm_flags,omitempty" yaml:"asm_flags,omitempty"`         //
	GccGoFlags  string   `json:"gcc_go_flags,omitempty" yaml:"gcc_go_flags,omitempty"`   //
	GCFlags     string   `json:"gc_flags,omitempty" yaml:"gc_flags,omitempty"`           //
	Compiler    string   `json:"compiler,omitempty" yaml:"compiler,omitempty"`           //
	Mod         string   `json:"mod,omitempty" yaml:"mod,omitempty"`                     //readonly, vendor, or mod
	ModFile     string   `json:"mod_file,omitempty" yaml:"mod_file,omitempty"`           //
	BuildMode   string   `json:"build_mode,omitempty" yaml:"build_mode,omitempty"`       //mode build mode to use. See 'go help buildmode' for more.
	LinkShared  bool     `json:"link_shared,omitempty" yaml:"link_shared,omitempty"`     //build code that will be linked against shared libraries previously created with -buildmode=shared.
	PkgDIR      string   `json:"pkg_dir,omitempty" yaml:"pkg_dir,omitempty"`             //install and load all packages from dir instead of the usual locations. For example, when building with a non-standard configuration, use -pkgdir to keep generated packages in a separate location.
	Tags        []string `json:"tags,omitempty" yaml:"tags,omitempty"`                   //
	TrimPath    bool     `json:"trim_path,omitempty" yaml:"trim_path,omitempty"`         //
	Static      bool     `json:"static,omitempty" yaml:"static,omitempty"`               //
	Files       []string `json:"files,omitempty" yaml:"files,omitempty"`                 //
	GOOS        string   `json:"goos,omitempty" yaml:"goos,omitempty"`                   //
	GOARCH      string   `json:"goarch,omitempty" yaml:"goarch,omitempty"`               //
	CGOEnabled  bool     `json:"cgo_enabled,omitempty" yaml:"cgo_enabled,omitempty"`     //
	GOPROXY     string   `json:"go_proxy,omitempty" yaml:"go_proxy,omitempty"`           //
	GO111MODULE string   `json:"go111_module,omitempty" yaml:"go111_module,omitempty"`   //
	UpxPack     bool     `json:"upx_pack,omitempty" yaml:"upx_pack,omitempty"`           //
}

func (b *Builder) ApplyToFlagSet(fs *cli.FlagSet) {
	fs.SortFlags = false

	fs.StringVar(&b.GOOS, "goos", b.GOOS, "目标操作系统: linux, darwin, windows")
	fs.StringVar(&b.GOARCH, "goarch", b.GOARCH, "目标 Arch: amd64, i386")
	fs.BoolVarP(&b.CGOEnabled, "cgo", "c", b.CGOEnabled, "允许CGO, 等同环境变量: CGO_ENABLED=1") //CGO_ENABLED

	fs.StringVarP(&b.Output, "output", "o", b.Output, "编译输出文件路径")
	fs.BoolVar(&b.TrimPath, "trimpath", b.TrimPath, "用包名替代本地文件路径") //remove all file system paths from the resulting executable. Instead of absolute file system paths, the recorded file names will begin with either "go" (for the standard library), or a module path@version (when using modules), or a plain import path (when using GOPATH).
	fs.BoolVar(&b.Static, "static", b.Static, "静态编译")
	fs.StringSliceVarP(&b.Tags, "tags", "t", b.Tags, "编译标签") //a comma-separated list of build tags to consider satisfied during the build. For more information about build tags, see the description of build constraints in the documentation for the go/build package. (Earlier versions of Go used a space-separated list, and that form is deprecated but still recognized.)

	fs.StringVar(&b.LDFlags, "ldflags", b.LDFlags, "对应 go build -ldflags: '[pattern=]arg list' arguments to pass on each go tool link invocation.")
	fs.StringVar(&b.ASMFlags, "asmflags", b.ASMFlags, "对应 go build -asmflags: '[pattern=]arg list' arguments to pass on each go tool asm invocation.")
	fs.StringVar(&b.GccGoFlags, "gccgoflags", b.GccGoFlags, "对应 go build -gccgoflags: '[pattern=]arg list' arguments to pass on each gccgo compiler/linker invocation.")
	fs.StringVar(&b.GCFlags, "gcflags", b.GCFlags, "对应 go build -gcflags: '[pattern=]arg list' arguments to pass on each go tool compile invocation.")
	fs.StringVar(&b.Compiler, "compiler", b.Compiler, "要使用的编译器的名称， `gccgo` 获取 `gc`") //"name of compiler to use, as in runtime.Compiler (gccgo or gc).")

	fs.BoolVar(&b.KeepWorkDIR, "work", b.KeepWorkDIR, "显示编译的临时目录并在编译完成后不删除") //print the name of the temporary work directory and do not delete it when exiting.
	fs.BoolVarP(&b.X, "detail", "x", b.X, "编译并显示详细的编译命令")                    //print the commands.
	fs.BoolVarP(&b.Verbose, "verbose", "v", b.Verbose, "显示编译的包名")            //print the names of packages as they are compiled.
	fs.BoolVarP(&b.NoCache, "no-cache", "a", b.NoCache, "不使用编译缓存")           //force rebuilding of packages that are already up-to-date.
	fs.BoolVarP(&b.NotRun, "not-run", "n", b.NotRun, "打印编译命令但不执行")           //print the commands but do not run them.
	fs.BoolVarP(&b.UpxPack, "upx", "u", b.UpxPack, "使用 upx 压缩输出文件")          //using upx package the output binary.
}

func (b *Builder) BuildFlagOption() cli.Option {
	return cli.Flags(b.ApplyToFlagSet)
}

func (b *Builder) GenerateArgs(output string) (args []string) {
	if b.GOOS != "" {
		_ = os.Setenv("GOOS", b.GOOS)
	}

	if b.GOARCH != "" {
		_ = os.Setenv("GOARCH", b.GOARCH)
	}

	if b.CGOEnabled {
		_ = os.Setenv("CGO_ENABLED", "1")
	} else {
		_ = os.Setenv("CGO_ENABLED", "0")
	}

	if b.GOPROXY != "" {
		_ = os.Setenv("GOPROXY", b.GOPROXY)
	}

	if b.GO111MODULE != "" {
		_ = os.Setenv("GO111MODULE", b.GO111MODULE)
	}

	args = []string{"build"}

	if b.Output != "" {
		output = b.Output
	}

	if output != "" {
		args = append(args, "-o", output)
	}

	if b.NotRun {
		args = append(args, "-n")
	}
	if b.NoCache {
		args = append(args, "-a")
	}
	if b.X {
		args = append(args, "-x")
	}
	if b.Verbose {
		args = append(args, "-v")
	}
	if b.KeepWorkDIR {
		args = append(args, "-work")
	}
	if b.ASMFlags != "" {
		args = append(args, "-asmflags", b.ASMFlags)
	}
	if b.GccGoFlags != "" {
		args = append(args, "-gccgoflags", b.GccGoFlags)
	}
	if b.GCFlags != "" {
		args = append(args, "-gcflags", b.GCFlags)
	}
	if b.Compiler != "" {
		args = append(args, "-compiler", b.Compiler)
	}
	if b.TrimPath {
		args = append(args, "-trimpath")
	}

	//ldflags
	{
		var ldFlags = []string{b.LDFlags}
		if b.Static {
			switch b.GOOS {
			case "darwin":
				ldFlags = append(ldFlags, "-extldflags '-sectcreate __TEXT __info_plist Info.plist'")
			case "linux":
				ldFlags = append(ldFlags, "-extldflags '-fno-PIC -static -Wl,-z,stack-size=8388608'", "-linkmode=external")
			case "windows":
				ldFlags = append(ldFlags, "-extldflags '-static'", "-H=windowsgui")
			default:
				ldFlags = append(ldFlags, "-extldflags '-static'")
			}
		}
		args = append(args, "-ldflags", strings.Join(ldFlags, " "))
	}

	//tags
	var tags = b.Tags
	if b.Static && b.GOOS == "linux" {
		tags = append(b.Tags, "netgo", "osusergo")
	}
	if len(tags) > 0 {
		args = append(args, "-tags", strings.Join(tags, ","))
	}

	if len(b.Files) > 0 {
		args = append(args, b.Files...)
	} else {
		args = append(args, ".")
	}
	return
}

func (b *Builder) FromEnv() *Builder {
	b.NotRun = env.GetBool("wena.build.not_run", b.NotRun)
	b.NoCache = env.GetBool("wena.build.no_cache", b.NoCache)
	b.Verbose = env.GetBool("wena.build.verbose", b.Verbose)
	b.X = env.GetBool("wena.build.detail", b.X)
	b.KeepWorkDIR = env.GetBool("wena.build.keep_dir", b.KeepWorkDIR)
	b.Race = env.GetBool("wena.build.race", b.Race)
	b.LDFlags = env.GetString("wena.build.ldflags", b.LDFlags)
	b.ASMFlags = env.GetString("wena.build.asmflags", b.ASMFlags)
	b.GccGoFlags = env.GetString("wena.build.gccgoflags", b.GccGoFlags)
	b.GCFlags = env.GetString("wena.build.gcflags", b.GCFlags)
	b.Compiler = env.GetString("wena.build.compiler", b.Compiler)
	b.Mod = env.GetString("wena.build.mod", b.Mod)
	b.ModFile = env.GetString("wena.build.modfile", b.ModFile)
	b.BuildMode = env.GetString("wena.build.mode", b.BuildMode)
	b.LinkShared = env.GetBool("wena.build.link_shared", b.LinkShared)
	b.PkgDIR = env.GetString("wena.build.pkg_dir", b.PkgDIR)
	b.Tags = env.GetStrings("wena.build.tags", b.Tags...)
	b.TrimPath = env.GetBool("wena.build.trim_path", b.TrimPath)
	b.Static = env.GetBool("wena.build.static", b.Static)
	b.GOOS = env.GetString("wena.build.goos", b.GOOS)
	b.GOARCH = env.GetString("wena.build.goarch", b.GOARCH)
	b.CGOEnabled = env.GetBool("wena.build.cgo", b.CGOEnabled)
	b.GOPROXY = env.GetString("GOPROXY", b.GOPROXY)
	b.GO111MODULE = env.GetString("GO111MODULE", b.GO111MODULE)
	b.UpxPack = env.GetBool("wena.build.upx", b.UpxPack)

	return b
}

func (b *Builder) FromYAML(fn string) *Builder {
	var v []byte
	var err error
	if v, err = os.ReadFile(fn); err == nil {
		err = yaml.Unmarshal(v, b)
	}

	if err != nil && !os.IsNotExist(err) {
		log.Printf("unmarshal .kagura.yml err: %+v\n", err)
	}

	return b
}
